package com.jacob.androidframe.base;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

/**
 * 本类是所有Adapter的父类，可以用于ListView、Spinner和GridView。
 * 用来封装和业务无关的代码。
 * 
 * @author 王超
 * 
 */
public abstract class AdapterBase<T> extends BaseAdapter
{
	// 上下文对象
	private Context			mContext;
	// 存放数据的List
	private List<T>			mItemList;
	// 用来根据Item布局文件生成View
	private LayoutInflater	mLayoutInflater;
	
	/**
	 * 构造Adapter。
	 * 
	 * @param pContext
	 *            上下文对象
	 * @param pList
	 *            数据源。子类Adapter的数据源若是其自己创建的，这里传入null。然后调用setList()方法设置数据源。
	 */
	public AdapterBase(Context pContext, List<T> pList)
	{
		super();
		setContext(pContext);
		setLayoutInflater(mContext);
		setItemList(pList);
	}
	
	public AdapterBase(Context pContext)
	{
		this(pContext, null);
	}

	/**
	 * 声明Item的ID是否是底层稳定的。
	 * 默认返回false，建议子类覆盖。
	 * 
	 * @return 若相同的ID一定表示相同的对象，返回true；否则返回false。
	 */
	@Override
	public boolean hasStableIds()
	{
		return super.hasStableIds();
	}

	/**
	 * 返回true表示所有Item都是可选择、可点击的。
	 * 若Item不是全部可选的，则子类需要重写本方法。
	 */
	@Override
	public boolean areAllItemsEnabled()
	{
		return super.areAllItemsEnabled();
	}

	/**
	 * 返回指定位置的Item是否可选择、可点击。
	 * 若指定位置的Item不是可选的，则子类需要重写本方法。
	 * 若位置是无效的时，结果是不确定的，这种情况下应该抛出一个ArrayIndexOutOfBoundsException。
	 */
	@Override
	public boolean isEnabled(int pPosition)
	{
		return super.isEnabled(pPosition);
	}

	/**
	 * <p>
	 * 返回{@link #getView}会创建几种View类型。每种类型代表一组在{@link #getView}中可以重用的View。
	 * 如果Adapter中所有Item使用的View类型相同，本方法应该返回1。（默认就是返回1。）
	 * 若子类重写了{@link #getItemViewType}，则也应该重写本方法。
	 * </p>
	 * <p>
	 * 只有当Adapter和{@link AdapterView}绑定时，本方法才会被调用。
	 * </p>
	 * 
	 * @return 这个Adapter将创建几种类型的View
	 */
	@Override
	public int getViewTypeCount()
	{
		return super.getViewTypeCount();
	}

	/**
	 * 传入位置，返回该位置的Item应该使用的View的类型。
	 * 在{@link #getView}中，调用这个方法，根据返回的类型值，创建不同的View。
	 * 若子类使用的List中包含多个类的对象，想要根据对象不同生成不同的View，需要重写本方法。
	 * 
	 * @param position
	 *            The position of the item within the adapter's data set whose view type we
	 *            want.
	 * @return 代表View类型的整型值。范围必须在0到{@link #getViewTypeCount} - 1之间。也可以返回{@link #IGNORE_ITEM_VIEW_TYPE}。
	 *         Two views should share the same type if one can be converted to the other in {@link #getView}.
	 * @see #IGNORE_ITEM_VIEW_TYPE
	 */
	@Override
	public int getItemViewType(int pPosition)
	{
		return super.getItemViewType(pPosition);
	}

	/**
     * 数据集中共有多少个Item。
     * 
     * @return Item的个数
     */
	@Override
	public int getCount()
	{
		return mItemList.size();
	}

	/**
     * 得到数据集中指定位置的Item对象
     * 
     * @param position 目标Item的位置
     * @return 指定位置的Item对象.
     */
	@Override
	public T getItem(int pPosition)
	{
		return mItemList.get(pPosition);
	}

	/**
	 * 得到和指定位置的Item的ID。
	 * 默认返回Item在List中的位置，建议子类覆盖。
	 * 
	 * AdapterView的getItemIdAtPosition()方法其实就是通过调用本方法实现的，
	 * 若本方法实现的不对的话，AdapterView.getItemIdAtPosition()将无法得到正确的结果。
	 */
	@Override
	public long getItemId(int pPosition)
	{
		return pPosition;
	}

	/**
	 * 返回View对象，其中显示指定位置的Item数据。
	 * 这个View对象既可以用代码创建，也可以用XML布局文件生成。
	 * 若想使用后一种方法生成View，应该使用{@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)}（第3个参数传入false），
	 * 否则ViewGroup会给View加上默认的布局参数。
	 * 
	 * 这个方法本类已经处理好了，子类需要重写的是createConvertVieW(int, ViewGroup)方法和afterCreateConvertView(int, View)方法。
     * 
     * @param pPosition 需要转换成View对象的Item的位置
     * @param pConvertView 可以复用的View对象。注意：在使用这个对象前，应该对其做非空和类型检查。
     * 如果这个对象无法正确显示数据，可以在这个方法中新建一个View。
     * 对于混合型的AdapterView，可以为其设置有几种ItemView类型（重写getViewTypeCount()方法和getItemViewType(int)方法），这样本参数就可以返回正确类型的View对象。
     * （参阅{@link #getViewTypeCount()}和{@link #getItemViewType(int)}）
     * @param pParent 父AdapterView对象，所有返回的子View最终都会被加入其中。
     * 
     * @return 显示指定位置的Item数据的View对象
     */
	@Override
	public View getView(int pPosition, View pConvertView, ViewGroup pParent) {
		if (pConvertView == null) {
			pConvertView = createConvertView(pPosition, pParent);
		}
		
		afterCreateConvertView(pPosition, pConvertView);
		
		return pConvertView;
	}

	/**
	 * 返回Spinner的下拉/弹出框所用的子View，其中显示指定位置的Item数据。
	 * 这个方法本类已经处理好了，子类需要重写的是createDropDownConvertView(int, ViewGroup)方法和afterCreateDropDownConvertView(int, View)方法。
	 * 
	 * @param pPosition 需要转换成View对象的Item的位置
	 * @param pConvertView 可以复用的View对象。注意：在使用这个对象前，应该对其做非空和类型检查。
     * 如果这个对象无法正确显示数据，可以在这个方法中新建一个View。
	 * @param pParent 父AdapterView对象，所有返回的子View最终都会被加入其中。
	 * @return 显示指定位置的Item数据的View对象（返回的View必须实现Checkable接口，
	 * 否则用于Spinner时，弹出对话框不会高亮显示当前选中的Item）
	 */
	@Override
	public View getDropDownView(int pPosition, View pConvertView, ViewGroup pParent)
	{
		if (pConvertView == null) {
			pConvertView = createDropDownConvertView(pPosition, pParent);
		}
		
		afterCreateDropDownConvertView(pPosition, pConvertView);
		
		return pConvertView;
	}

	/**
	 * 设置数据源，此方法应该在生成Adapter时调用。
	 * 
	 * @param pItemList
	 *            包含数据的List。
	 */
	public void setItemList(List<T> pItemList)
	{
		if (pItemList != null) {
			mItemList = new ArrayList<T>(pItemList);
		} else {
			mItemList = new ArrayList<T>();
		}
		notifyDataSetChanged();
	}
	
	public void addItemList(List<T> pItemList)
	{
		if (pItemList != null) {
			mItemList.addAll(pItemList);
		}
		notifyDataSetChanged();
	}

	public List<T> getItemList()
	{
		return mItemList;
	}
	
	public void clearItemList()
	{
		mItemList.clear();
		notifyDataSetChanged();
	}
	
	/**
	 * 创建Spinner的下拉ItemView。
	 * 本方法默认调用createConvertView(int, ViewGroup)。
	 * 
	 * @param pPosition 需要转换成View对象的Item的位置
	 * @param pParent 父AdapterView对象，所有返回的子View最终都会被加入其中。
	 * @return 显示指定位置的Item数据的View对象（返回的View必须实现Checkable接口，
	 * 否则用于Spinner时，弹出对话框不会高亮显示当前选中的Item）
	 */
	protected View createDropDownConvertView(int pPosition, ViewGroup pParent) {
		return createConvertView(pPosition, pParent);
	}
	
	/**
	 * 下拉ItemView准备好后，就会回调本方法。无论下拉ItemView是新建的，还是复用的，本方法都会被调用。
	 * 一般应在本方法中对View对象进行处理（使用ViewHolder.get(View, int)方法得到ChildView对象），然后该View就会被加入到AdapterView中。
	 * 本方法默认调用afterCreateConvertView(int, View)。
	 * 
	 * @param pPosition pConvertView对象对应的Item的位置
	 * @param pConvertView 复用的View对象。注意：这个对象一定不为null。同时，如果正确重写了getViewTypeCount()方法和getItemViewType(int)方法，也不用检查本对象的类型。
	 */
	protected void afterCreateDropDownConvertView(int pPosition, View pConvertView) {
		afterCreateConvertView(pPosition, pConvertView);
	}

	/**
	 * 创建ItemView。
	 * 只有在需要新建ItemView的时候，才会回调本方法。如果ItemView可以复用，则本方法不会被调用。
	 * 
	 * @param pPosition 需要转换成View对象的Item的位置
	 * @param pParent 父AdapterView对象，所有返回的子View最终都会被加入其中。
	 * @return 显示指定位置的Item数据的View对象（返回的View必须实现Checkable接口，
	 * 否则用于Spinner时，弹出对话框不会高亮显示当前选中的Item）
	 */
	protected abstract View createConvertView(int pPosition, ViewGroup pParent);
	
	/**
	 * ItemView准备好后，就会回调本方法。无论ItemView是新建的，还是复用的，本方法都会被调用。
	 * 一般应在本方法中对View对象进行处理（使用ViewHolder.get(View, int)方法得到ChildView对象），然后该View就会被加入到AdapterView中。
	 * 
	 * @param pPosition pConvertView对象对应的Item的位置
	 * @param pConvertView 复用的View对象。注意：这个对象一定不为null。同时，如果正确重写了getViewTypeCount()方法和getItemViewType(int)方法，也不用检查本对象的类型。
	 */
	protected abstract void afterCreateConvertView(int pPosition, View pConvertView);

	protected View inflateItemLayout(int pLayoutResId, ViewGroup pParent) {
		return mLayoutInflater.inflate(pLayoutResId, pParent, false);
	}
	
	protected Context getContext()
	{
		return mContext;
	}

	protected LayoutInflater getLayoutInflater()
	{
		return mLayoutInflater;
	}

	private void setContext(Context pContext)
	{
		mContext = pContext;
	}

	private void setLayoutInflater(Context pContext)
	{
		mLayoutInflater = LayoutInflater.from(pContext);
	}
	
	protected static final class ViewHolder {
		@SuppressWarnings("unchecked")
		public static <T extends View> T get(View pConvertView, int pChildViewId) {
			SparseArray<View> _ViewHolder = (SparseArray<View>) pConvertView.getTag();
			if (_ViewHolder == null) {
				_ViewHolder = new SparseArray<View>();
				pConvertView.setTag(_ViewHolder);
			}
			
			View _ChildView = _ViewHolder.get(pChildViewId);
			if (_ChildView == null) {
				_ChildView = pConvertView.findViewById(pChildViewId);
				_ViewHolder.put(pChildViewId, _ChildView);
			}
			
			return (T) _ChildView;
		}
	}

}
